﻿using Recipes.Logic;
using Recipes.Model;
using Recipes.ViewModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using Windows.ApplicationModel.Search;
using Windows.Data.Xml.Dom;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.System;
using Windows.UI.ApplicationSettings;
using Windows.UI.Notifications;
using Windows.UI.StartScreen;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234237

namespace Recipes
{
    /// <summary>
    /// A basic page that provides characteristics common to most applications.
    /// </summary>
    public sealed partial class MainPage : Recipes.Common.LayoutAwarePage, INotifyPropertyChanged
    {
        //Oggetto che consente di scaricare le ricette
        private RecipeDownloader _recipeDownloader;

        private ObservableCollection<ShortRecipe> _recipes;
        /// <summary>
        /// Collezione delle ricette da mostrare nella pagina principale.
        /// </summary>        
        public ObservableCollection<ShortRecipe> Recipes
        {
            get { return _recipes; }
            set
            {
                _recipes = value;
                notifyPropertyChanged("Recipes");
            }
        }
       
        private ObservableCollection<SemanticGroup> _semanticGroups;
        /// <summary>
        /// Collezione dei grupi da mostrare quando il semantic zoom è attivo.
        /// </summary>
        public ObservableCollection<SemanticGroup> SemanticGroups
        {
            get { return _semanticGroups; }
            set
            {
                _semanticGroups = value;
                notifyPropertyChanged("SemanticGroup");
            }
        }

        /// <summary>
        /// Costruttore della pagina principale.
        /// </summary>
        public MainPage()
        {
            this.InitializeComponent();

            DataContext = this;
            _recipeDownloader = new RecipeDownloader();
            Loaded += MainPage_Loaded;
            
            //Imposto gli handler per il pannello di ricerca
            SearchPane.GetForCurrentView().SuggestionsRequested += MainPage_SuggestionsRequested;
            SearchPane.GetForCurrentView().QuerySubmitted += MainPage_QuerySubmitted;

            //Imposto gli handler per il pannello dei settings
            SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested;
        }

        /// <summary>
        /// Metodo invocato quando viene aperto il charm dei Settings
        /// </summary>
        /// <param name="sender">Il pannello che ha invocato il metodo</param>
        /// <param name="args">I command che sono impostati nel cherm settings</param>
        void MainPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
        {
            //Aggiungo un bottone al pannello dei comandi
            args.Request.ApplicationCommands.Add(
                new SettingsCommand("LinkCmd", "Vai al sito", //Il bottone ha un identificativo e mostra una stringa all'utente
                    //quando viene premuto effettua un'azione.
                async a => await Launcher.LaunchUriAsync(new Uri("http://www.4yougratis.it/ricette_cucina/"))));

                
        }

        /// <summary>
        /// Metodo che viene invocato quando l'utente invia una ricerca al sistema operativo
        /// </summary>
        /// <param name="sender">Il pannello di ricerca che ha inviato la richiesta</param>
        /// <param name="args">I parametri della richiesta</param>
        void MainPage_QuerySubmitted(SearchPane sender, SearchPaneQuerySubmittedEventArgs args)
        {
            //Effettuo la ricerca sulle ricette che ho scaricato
            IList<string> titles = App.downloadedRecipes
                .Where(x => x.Title.ToLower().Contains(args.QueryText.ToLower()))
                .Select(x => x.Title).Distinct().ToList();
            
            //Se ho trovato una sola ricetta, porto l'utente direttamente alla pagina della ricetta.
            if (titles.Count == 1)
            {
                //Ottengo la ricetta
                var recipe = App.downloadedRecipes.Where(x => x.Title.ToLower().Contains(args.QueryText.ToLower())).FirstOrDefault();
                if (recipe != null)
                {
                    //Se la ricetta esiste, navigo alla pagina e mostro quella specifica ricetta
                    Frame.Navigate(typeof(Pages.RecipePage), recipe.Guid);
                }
            }
            else
            {
                //Se ci sono più ricette, navigo ad una pagina che le possa mostrare tutte
                Frame.Navigate(typeof(Pages.SearchPage), args.QueryText);
            }
        }

        /// <summary>
        /// Metodo invocato quando l'utente digita una lettera nel campo di ricerca del sistema.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void MainPage_SuggestionsRequested(Windows.ApplicationModel.Search.SearchPane sender, Windows.ApplicationModel.Search.SearchPaneSuggestionsRequestedEventArgs args)
        {
            //Cerco i titoli che possono avere una relazione con la ricerca inserita dall'utente. 
            IList<string> suggestions = App.downloadedRecipes
                .Where(x => x.Title.ToLower().Contains(args.QueryText.ToLower())).Select(x => x.Title)
                .Distinct().Take(5).ToList(); //Take(5) dice di prendere i primi 5 risultati. 
            //Nel pannello di ricerca si possono inserire al massimo 5 valori.

            //Imposto i suggerimenti nella SearchSuggestionCollection che verrà mostrata all'utente.
            args.Request.SearchSuggestionCollection.AppendQuerySuggestions(suggestions);
        }

        /// <summary>
        /// Metodo invocato quando viene caricata e mostrata la pagina all'utente
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        async void  MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            await cleanOldFiles();
        }

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="navigationParameter">The parameter value passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
        /// </param>
        /// <param name="pageState">A dictionary of state preserved by this page during an earlier
        /// session.  This will be null the first time a page is visited.</param>
        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
        protected override void SaveState(Dictionary<String, Object> pageState)
        {
        }

        /// <summary>
        /// Metodo invocato quando l'utente arriva alla pagina, sia perchè l'app viene lanciata per la prima volta
        /// sia perchè naviga indietro.
        /// Viene utilizzato per inizializzare gli oggetti necessari.
        /// </summary>
        /// <param name="e"></param>
        protected async override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            //Scarica le ricette
            RecipeDownloader downloader = new RecipeDownloader();
            App.downloadedRecipes = await downloader.DownloadAllRecipes();
            
            //Imposta la pagina principale
            setupStartPage();

            //Imposta i gruppi semantici
            setupSemanticGroups();

            //Imposta la coda delle notifiche
            setupNotificationQueue();
            
        }

        /// <summary>
        /// Metodo che imposta la pagina principale, preparando le tre notizie più recenti per categoria
        /// </summary>
        private void setupStartPage()
        {
            //Dizionario utilizzato per contare quante ricette per categoria si sono 
            //inserite nell'elenco di ricette da mostrare.
            IDictionary<Category, int> countRecipes = new Dictionary<Category, int>();
            //Elenco delle ricette da mostrare.
            IList<ShortRecipe> shortRecipes = new List<ShortRecipe>();

            foreach (Recipe r in App.downloadedRecipes) //Per ogni ricetta tra quelle scaricate
            {
                if (countRecipes.ContainsKey(r.FeedCategory)) 
                {
                    //Se il dizionario contiene la categoria a cui apartiene la ricetta
                    if (countRecipes[r.FeedCategory] < 3)
                    {
                        //Se ho inserito meno di 3 ricette
                        //Aggiungi la ricetta
                        shortRecipes.Add(new ShortRecipe(r));
                        //Prendi il numero di ricette inserite sinora
                        int value = countRecipes[r.FeedCategory];
                        value++; //Incrementalo di una unità
                        //Rimuovi e inserisci la categoria con il nuovo valore.
                        countRecipes.Remove(r.FeedCategory);
                        countRecipes[r.FeedCategory] = value;
                    }
                }
                else
                {
                    //Se il dizionario non contiene la categoria a cui appartiene la ricetta

                    //Crea e aggiungi la ricetta
                    ShortRecipe sr = new ShortRecipe(r);
                    shortRecipes.Add(sr);
                    //Inserisci la categoria nel dizionario con valore pari a 1
                    //(1 sola  ricetta inserita)
                    countRecipes[r.FeedCategory] = 1;
                }

            }

            //Creo i gruppi da mostrare nella Grid View
            this.GroupedSource.Source = shortRecipes
                .OrderBy(x => x.PublishDate)
                .GroupBy(x => x.FeedCategory)
                .OrderBy(x => x.Key, new RecipeCategoryComparer())
                .ToList();
        }

        /// <summary>
        /// Metodo utilizzato per creare e impostare i gruppi da mostrare quando lo zoom semantico è attivo
        /// </summary>
        private void setupSemanticGroups()
        {
            //Creo la lista dei gruppi
            List<SemanticGroup> semGroup = new List<SemanticGroup>();
            foreach (var c in Enum.GetValues(typeof(Category)))
            {
                //Per ogni valore delle categorie
                //conto quanti elementi ha ogni categoria
                int count = App.downloadedRecipes.Where(x => x.FeedCategory == (Category)c).Count();
                //Creo un oggetto che rappresenta la categoria in questione
                SemanticGroup sg = new SemanticGroup()
                {
                    Name = ((Category)c).ToString().Replace("_", " "),
                    Count = count
                };
                //Aggiungo la categoria ai gruppi semantici
                semGroup.Add(sg);
            }
            //Imposto i gruppi semantici perchè lo zoom possa mostrarli.
            SemanticGroups = new ObservableCollection<SemanticGroup>(semGroup);
        }

        /// <summary>
        /// Metodo utilizzato per impostare le notifiche da mostrare nella live tile.
        /// </summary>
        private void setupNotificationQueue()
        {
            //Ottengo l'updater
            TileUpdater updater = TileUpdateManager.CreateTileUpdaterForApplication();
            //Abilito la coda di notifiche
            updater.EnableNotificationQueue(true);
            //Elimino eventuali notifiche presenti nel precedente updater.
            updater.Clear();

            //Se ci sono ricette scaricate
            if (App.downloadedRecipes != null)
            {
                //Ottengo le tre ricette più nuove 
                List<Recipe> l = App.downloadedRecipes
                    .Where(x => x.FeedCategory == Category.New)
                    .Take(3).ToList();
                //Il contatore serve per costruire un identificativo univoco della notifica
                int counter = 0;
                foreach (var r in l)
                {
                    //Creo la notifica
                    TileNotification not = CreateTextNotification(r);
                    //Imposto l'id univoco della notifica
                    not.Tag = counter.ToString();
                    //Inserisco la notifica nella coda di quelle da mostrare
                    updater.Update(not);
                    //Aumento il contatore per la prossima notifica
                    counter++;
                }
            }

                        
        }

        /// <summary>
        /// Metodo helper per costruire le notifiche
        /// </summary>
        /// <param name="r">La ricetta da utilizzare per costruire la notifica</param>
        /// <returns>La notifica da mostrare</returns>
        private TileNotification CreateTextNotification(Recipe r)
        {
            //Ottengo una rappresentazione XML della notifica
            XmlDocument xDoc = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWideText03);
            //Ottengo tutti i campi di tipo text presenti nella notifica XML
            var title = xDoc.GetElementsByTagName("text");
            //Al posto del primo campo di tipo text, inserisco il titolo della ricetta
            title[0].InnerText = r.Title;
            
            //Costruisco la notifica a partire dalla rappresentazione XML
            TileNotification notification = new TileNotification(xDoc);
            //Restituisco la notifica al chiamante
            return notification;
        }

        /// <summary>
        /// Metodo di utilità che serve a rimuovere file vecchi se l'utente ha unpinnato la relativa 
        /// Tile secondaria dal menu start.
        /// </summary>
        /// <returns></returns>
        private async Task cleanOldFiles()
        {
            //Ottengo la lista delle tile secondarie attualmente presenti
            IReadOnlyList<SecondaryTile> createdTiles = await SecondaryTile.FindAllAsync();
            //Ottengo la lista dei file creati nella cartella di storage locale
            StorageFolder folder = ApplicationData.Current.LocalFolder;
            IReadOnlyList<StorageFile> createdFiles = await folder.GetFilesAsync();

            //Per ogni file creato
            foreach (StorageFile sf in createdFiles)
            {
                //Se non esiste una tile con id uguale al nome del file
                //allora non è più possibile aprire quel file dall'app e lo posso eliminare.
                if (createdTiles.Where(x => x.TileId == sf.DisplayName).Count() == 0)
                {
                    await sf.DeleteAsync();
                }
            }
        }

        /// <summary>
        /// Evento dell'interfaccia INotifyPropertyChanged
        /// Necessario per collegare la UI con il codice.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        private void notifyPropertyChanged(string p)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(p));
        }

        /// <summary>        
        /// Metodo invocato quando l'utente tocca un elemento
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Element_Tapped(object sender, TappedRoutedEventArgs e)
        {
            //Ottengo lid della ricetta a cui navigare
            Grid s = sender as Grid;
            Guid guid = (Guid)s.Tag;
            //Navigo alla ricetta
            this.Frame.Navigate(typeof(Pages.RecipePage), guid );
        }

        /// <summary>
        /// Metodo invocato quando un utente clicca sul nome del gruppo
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Header_Tapped(object sender, TappedRoutedEventArgs e)
        {
            //Ottengo il nome del gruppo verso cui navigare
            TextBlock tb = sender as TextBlock;
            string header = tb.Text;
            //Navigo ad una pagina che mostra gli elementi del gruppo
            Frame.Navigate(typeof(Pages.ListPage), header);
        }

        /// <summary>
        /// Metodo invocato quando un utente clicca sul gruppo nello zoom semantico
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Group_Tapped(object sender, TappedRoutedEventArgs e)
        {
            //Ottengo il nome del gruppo verso cui navigare
            Grid grid = sender as Grid;
            string header = grid.Tag as string;
            //Navigo ad una pagina che mostra gli elementi del gruppo
            Frame.Navigate(typeof(Pages.ListPage), header);
        }


    }
}
